package com.icesoft.core.dao.criteria;

import javax.persistence.Transient;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SelectBuilder implements Cloneable {

    private Class<?> selectClazz;
    private String func;
    private List<String> selectFields = new ArrayList<>();

    boolean isCount = false;

    @Override
    public SelectBuilder clone() {
        SelectBuilder selectBuilder = new SelectBuilder();
        selectBuilder.selectClazz = selectClazz;
        selectBuilder.func = func;
        selectBuilder.selectFields.addAll(selectFields);
        selectBuilder.isCount = isCount;
        selectBuilder.fromBuilder = fromBuilder.clone();
        return selectBuilder;
    }

    public static SelectBuilder selectFrom(FromBuilder fromBuilder) {
        return new SelectBuilder().builder(fromBuilder);
    }

    public static SelectBuilder select(Class<?> selectClazz) {
        return new SelectBuilder(selectClazz);
    }

    public static SelectBuilder select(String selectField) {
        return new SelectBuilder(null, selectField);
    }

    public static SelectBuilder select(String func, String... selectFields) {
        return new SelectBuilder(func, selectFields);
    }

    public static SelectBuilder isExist() {
        return SelectBuilder.select("1");
    }

    public static SelectBuilder count() {
        return SelectBuilder.select("count", "*");
    }

    public SelectBuilder() {
    }

    public SelectBuilder(Class<?> selectClazz) {
        this.selectClazz = selectClazz;
    }

    public SelectBuilder(String func, String... selectFields) {
        this.func = func;
        if ("count".equals(func)) {
            isCount = true;
        }
        for (String field : selectFields) {
            this.selectFields.add(field);
        }
    }

    FromBuilder fromBuilder = FromBuilder.get();

    public FromBuilder getFromBuilder() {
        return fromBuilder;
    }

    public SelectBuilder builder(QueryBuilder queryBuilder) {
        return builder(queryBuilder.getFromBuilder());
    }

    public SelectBuilder builder(FromBuilder fromBuilder) {
        this.fromBuilder = fromBuilder;
        bindSelect(fromBuilder);
        return this;
    }

    private void bindSelect(FromBuilder fromBuilder) {
        if (selectClazz == null) {
            return;
        }
        func = "new " + selectClazz.getName();
        Field[] fields = selectClazz.getDeclaredFields();
        Map<Class<?>, String> map = fromBuilder.getTableMap();
        for (Field field : fields) {
            // 排除$jacocoData字段
            if (field.getName().startsWith("$")) {
                continue;
            }
            if (field.isAnnotationPresent(Transient.class)) {
                continue;
            }
            String name = fromBuilder.getTableName() + "." + field.getName();
            FieldRef fieldRef = field.getAnnotation(FieldRef.class);
            if (fieldRef != null) {
                String nickname = map.get(fieldRef.clazz());
                if (nickname != null) {
                    name = nickname + "." + fieldRef.name();
                } else {
                    name = fromBuilder.getTableName() + "." + fieldRef.name();
                }
                if (!fieldRef.func().isEmpty()) {
                    name = fieldRef.func() + "(" + name + ")";
                }
            }
            selectFields.add(name);
        }
    }

    private static final String HQL_SELECT_STRING = "select ";

    public String getHql() {
        String pre = HQL_SELECT_STRING + (fromBuilder.distinct ? "distinct " : "");
        if (selectFields.isEmpty()) {
            return pre + fromBuilder.getTableName();
        }
        StringBuilder fieldNames = new StringBuilder();
        for (String field : selectFields) {
            fieldNames.append(fromBuilder.appendNickName(field)).append(",");
        }
        fieldNames.deleteCharAt(fieldNames.length() - 1);
        if (func == null) {
            return pre + fieldNames.toString();
        }
        return pre + func + "(" + fieldNames.toString() + ")";
    }

    @Override
    public String toString() {
        return getHql();
    }
}
